﻿// reference: https://github.com/omacarena/novaburst.ModularTypeScript/blob/master/novaburst.ModularTypeScript/NovaBurst.ModularTypeScript.Core.UI/modules/Core/Module/ModuleLoaderConfig.ts

module Sustainalytics.Module {

    export class ModuleLoaderConfig {

        /*
         * Singleton instance.
         */
        public static current: ModuleLoaderConfig = new ModuleLoaderConfig();

        /*
         * Whether to use bundles when solving modules or bundlings.
         */
        public useBundles: boolean = true;


        // module definitions
        private moduleDefinitions: IModuleDefinition[];    

        // module bundling maps
        private moduleBundleMaps: IModuleBundleMap[];

        // mapping between module name and module definition + bundle maps
        private moduleDefinitionsBundleMap: any = {};

        // mapping between bundling path and bundling definition
        private moduleBundleMapsMap: any = {};

        // promise for loading data (modules and bundlings) from JSON
        private loadFromJsonPromise: JQueryPromise<any>;

        private progressCallbacks: ((doneCount: number, totalCount: number) => void)[] = [];


        // progress callback function
        public subscribeToProgress(callback: (doneCount: number, totalCount: number) => void) {
            var ctx = this;

            ctx.progressCallbacks.push(callback);
        }

        // notify progress
        public notifyProgress(doneCount: number, totalCount: number) {
            var ctx = this;

            if (ctx.progressCallbacks && ctx.progressCallbacks.length > 0) {
                ctx.progressCallbacks.forEach(callback => {
                    callback(doneCount, totalCount);
                });
            }
        }


        public getLoadFromJsonPromise(): JQueryPromise<any> {
            return this.loadFromJsonPromise ? this.loadFromJsonPromise : $.when(null);
        }

        /*
         * Load module definitions and bundle maps from JSON.
         */
        public loadFromJson(modDefsPath: string, modBundlesPath: string): JQueryPromise<any> {
            var ctx = this;

            var def = $.Deferred();

            ctx.loadFromJsonPromise = def;

            // get module definitions
            var getModDefsPromise = $.get(modDefsPath);
            
            // success
            getModDefsPromise.done(modDefinitions => {

                // set data
                ctx.moduleDefinitions = modDefinitions;


                // update scripts
                if (ctx.moduleDefinitions && ctx.moduleDefinitions.length > 0) {

                    ctx.moduleDefinitions.forEach(modDef => {

                        var scriptsTyped: IModuleDefinitionScript[] = [];

                        if (modDef['scripts'] && modDef['scripts'].length > 0) {
                            (<any[]>modDef['scripts']).forEach(script => {
                                
                                if (typeof (script) == 'string') {
                                    scriptsTyped.push(
                                        {
                                            path: script,
                                            browser: []
                                        });
                                }
                                else {
                                    scriptsTyped.push(script);
                                }
                            });
                        }

                        modDef.scriptsTyped = scriptsTyped;
                    });
                }


                // get module bundle maps
                var getModBundlesPromise = $.get(modBundlesPath);

                // suceess
                getModBundlesPromise.done(bundleMaps => {

                    // set data
                    ctx.moduleBundleMaps = bundleMaps;

                    // update map
                    ctx.createModuleDefinitionsBundlesMap();

                    def.resolve();
                });

                // error
                getModBundlesPromise.fail(function () {
                    def.reject();
                });
            });

            // error
            getModDefsPromise.fail(function () {
                def.reject();
            });

            return def;
        }

        /*
         * Get module definition info (module definition data and module bundle maps).
         */
        public getModuleDefinitionInfo(moduleName: string): IModuleDefinitionInfo {
            var ctx = this;

            // format name
            moduleName = moduleName.toLowerCase();

            // search map
            var mapData: IModuleDefinitionInfo = ctx.moduleDefinitionsBundleMap[moduleName];

            return mapData;
        }

        /*
         * Get module bundle map info (bundle map data).
         */
        public getModuleBundleMapInfo(moduleBundleMapPath: string): IModuleBundleMapInfo {
            var ctx = this;

            // format name
            moduleBundleMapPath = moduleBundleMapPath.toLowerCase();

            // search map
            var mapData: IModuleBundleMapInfo = ctx.moduleBundleMapsMap[moduleBundleMapPath];

            return mapData;
        }


        private createModuleDefinitionsBundlesMap() {
            var ctx = this;

            // init
            ctx.moduleDefinitionsBundleMap = {};
            ctx.moduleBundleMapsMap = {};

            // alias
            var modDefMap = ctx.moduleDefinitionsBundleMap;
            var modBundleMapMap = ctx.moduleBundleMapsMap;
            var modDefs = ctx.moduleDefinitions;
            var modBundleMaps = ctx.moduleBundleMaps;

            // add module definitions to map
            if (modDefs && modDefs.length > 0) {

                modDefs.forEach(modDef => {

                    var mapData: IModuleDefinitionInfo =
                        {
                            moduleDefinition: modDef,
                            moduleBundleMaps: []
                        };

                    modDefMap[modDef.moduleName.toLowerCase()] = mapData;
                });
            }

            // add bundle maps to map
            if (modBundleMaps && modBundleMaps.length) {

                modBundleMaps.forEach(modBundleMap => {

                    // create bundle map map data
                    var modBundleMapInfo: IModuleBundleMapInfo =
                        {
                            moduleBundleMap: modBundleMap
                        };

                    // update bundle maps map
                    modBundleMapMap[modBundleMap.relativeOutPath.toLowerCase()] = modBundleMapInfo;

                    if (modBundleMap.modules && modBundleMap.modules.length > 0) {
                        modBundleMap.modules.forEach(modName => {

                            // get data
                            var modDefInfo = ctx.getModuleDefinitionInfo(modName);

                            // update module definitions map data
                            modDefInfo.moduleBundleMaps.push(modBundleMap);
                        });
                    }
                });
            }
        }
    }


    export interface IModuleDefinitionInfo {

        moduleDefinition: IModuleDefinition;
        moduleBundleMaps: IModuleBundleMap[];
    }


    export interface IModuleDefinition {

        moduleName: string;
        dependencies: string[];
        scriptsTyped: IModuleDefinitionScript[];
    }


    export interface IModuleBundleMapInfo {
        moduleBundleMap: IModuleBundleMap;
    }


    export interface IModuleBundleMap {

        modules: string[];
        relativeFilePaths: string[];
        relativeOutPath: string;
        dependencies?: string[];
        ignoreModuleDependencies?: boolean;
        browser?: IScriptBrowser[];
    }


    export interface IModuleDefinitionScript {

        path: string;
        browser: IScriptBrowser[];
    }


    export interface IScriptBrowser {

        name: string;
        vers?: string;
        comp?: string;
        inverse?: boolean;
    }


    /**
    Loading AngularJS Components After Your Application Has Been Bootstrapped
    http://www.bennadel.com/blog/2553-loading-angularjs-components-after-your-application-has-been-bootstrapped.htm
*/
    export class ModularAngular {

        public static config(mod: ng.IModule, inputInlineAnnotatedFunc?: any[]): ng.IModule {

            // annotations valid for current custom config function
            var localAnnotations =
                [
                    '$controllerProvider',
                    '$provide',
                    '$compileProvider',
                    '$filterProvider'
                ];

            // annotations valid for entire config
            var annotations = [];
            annotations.push.apply(annotations, localAnnotations);

            var userFunc: any = null;

            if (inputInlineAnnotatedFunc && inputInlineAnnotatedFunc.length > 0) {

                // user defined function is the last in the annotation array
                userFunc = inputInlineAnnotatedFunc[inputInlineAnnotatedFunc.length - 1];

                // get arguments from the annotation array except the last element which is the function to call
                inputInlineAnnotatedFunc.forEach(function (inputArg, index) {
                    if (index < inputInlineAnnotatedFunc.length - 1) {

                        // add function annotation
                        annotations.push(inputArg);
                    }
                });
            }


            // configuration function to be called
            function configFunc($controllerProvider: ng.IControllerProvider, $provide: ng.IServiceProvider, $compileProvider: ng.ICompileProvider, $filterProvider: ng.IFilterProvider) {

                // keep old values
                mod['_controller'] = mod.controller;
                mod['_service'] = mod.service;
                mod['_factory'] = mod.factory;
                mod['_value'] = mod.value;
                mod['_directive'] = mod.directive;
                mod['_filter'] = mod.filter;


                // lazy controller registration
                (<any>mod)['controller'] = function (name, ctor) {
                    $controllerProvider.register(name, ctor);
                    return (this);
                };

                // lazy service registration
                (<any>mod)['service'] = function (name, ctor) {
                    $provide['service'](name, ctor);
                    return (this);
                };

                // lazy factory registration
                (<any>mod)['factory'] = function (name, factory) {
                    $provide['factory'](name, factory);
                    return (this);
                };

                // lazy value registration
                (<any>mod)['value'] = function (name, factory) {
                    $provide['value'](name, factory);
                    return (this);
                };

                // lazy directive registration
                (<any>mod)['directive'] = function (name, factory) {
                    $compileProvider.directive(name, factory);
                    return (this);
                };

                // lazy filter registration
                (<any>mod)['filter'] = function (name, factory) {
                    $filterProvider.register(name, factory);
                    return (this);
                };


                // call user defined func
                if (userFunc) {

                    // determine user function arguments
                    var userFuncArgs: any[] = <any[]><any>arguments;
                    userFuncArgs = userFuncArgs.slice(localAnnotations.length, userFuncArgs.length - 1);

                    // call user function
                    userFunc.apply(mod, userFuncArgs);
                }
            }


            // describe function arguments
            configFunc['$inject'] = annotations;


            // apply config function to current module
            return mod.config(configFunc);
        }
    }
}